package mogiksaop.qliphoth.client.models.smd;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import org.lwjgl.util.vector.Matrix4f;

import mogiksaop.qliphoth.client.models.animations.EnumGeomData;
import mogiksaop.qliphoth.client.models.animations.IModulized;
import mogiksaop.qliphoth.common.util.helpers.VectorHelper;

public class Bone implements IModulized {
	public Bone copy;
	public String name;
	public int ID;
	public Bone parent;
	public SmdModel owner;
	private Boolean isDummy;
	public Matrix4f rest;

	public Bone(String name, int ID, Bone parent, SmdModel owner) {
		this.copy = null;

		this.modified = new Matrix4f();

		this.difference = new Matrix4f();

		this.prevInverted = new Matrix4f();

		this.children = new ArrayList();
		this.verts = new HashMap();
		this.animatedTransforms = new HashMap();

		this.name = name;
		this.ID = ID;
		this.parent = parent;
		this.owner = owner;
	}

	public Matrix4f restInverted;
	public Matrix4f modified;
	public Matrix4f difference;
	public Matrix4f prevInverted;
	public ArrayList<Bone> children;
	public HashMap<DeformVertex, Float> verts;
	public HashMap<String, HashMap<Integer, Matrix4f>> animatedTransforms;

	public Bone(Bone b, Bone parent, SmdModel owner) {
		this.copy = null;
		this.modified = new Matrix4f();
		this.difference = new Matrix4f();
		this.prevInverted = new Matrix4f();
		this.children = new ArrayList();
		this.verts = new HashMap();
		this.animatedTransforms = new HashMap();
		this.name = b.name;
		this.ID = b.ID;
		this.owner = owner;
		this.parent = parent;

		for (Map.Entry<DeformVertex, Float> entry : b.verts.entrySet()) {
			this.verts.put(owner.verts.get(((DeformVertex) entry.getKey()).ID), entry.getValue());
		}
		this.animatedTransforms = new HashMap(b.animatedTransforms);
		this.restInverted = b.restInverted;
		this.rest = b.rest;
		b.copy = this;
	}

	public void setChildren(Bone b, ArrayList<Bone> bones) {
		for (int i = 0; i < b.children.size(); i++) {
			Bone child = (Bone) b.children.get(i);
			this.children.add(bones.get(child.ID));
			((Bone) bones.get(child.ID)).parent = this;
		}
	}

	public boolean isDummy() {
		if (this.isDummy == null) {
		} else {
		}
		return this.isDummy.booleanValue();
	}

	public void setRest(Matrix4f resting) {
		this.rest = resting;
	}

	public void addChild(Bone child) {
		this.children.add(child);
	}

	public void addVertex(DeformVertex v, float weight) {
		if (this.name.equals("blender_implicit"))
			throw new UnsupportedOperationException("NO.");
		this.verts.put(v, Float.valueOf(weight));
	}

	private void reform(Matrix4f parentMatrix) {
		this.rest = Matrix4f.mul(parentMatrix, this.rest, null);
		if (ValveStudioModel.debugModel)
			VectorHelper.print(this.name + ' ' + this.rest);
		reformChildren();
	}

	public void reformChildren() {
		for (Bone child : this.children) {
			child.reform(this.rest);
		}
	}

	public void invertRestMatrix() {
		this.restInverted = Matrix4f.invert(this.rest, null);
	}

	public void reset() {
		this.modified.setIdentity();
	}

	public void preloadAnimation(AnimFrame key, Matrix4f animated) {
		HashMap<Integer, Matrix4f> precalcArray;
		if (this.animatedTransforms.containsKey(key.owner.animationName)) {
			precalcArray = (HashMap) this.animatedTransforms.get(key.owner.animationName);
		} else {
			precalcArray = new HashMap<Integer, Matrix4f>();
		}

		precalcArray.put(Integer.valueOf(key.ID), animated);
		this.animatedTransforms.put(key.owner.animationName, precalcArray);
	}

	public void setModified() {
		Matrix4f real, realInverted;
		if (this.owner.owner.hasAnimations() && this.owner.currentAnim != null) {
			AnimFrame currentFrame = (AnimFrame) this.owner.currentAnim.frames
					.get(this.owner.currentAnim.currentFrameIndex);
			realInverted = (Matrix4f) currentFrame.transforms.get(this.ID);
			real = (Matrix4f) currentFrame.invertTransforms.get(this.ID);
		} else {
			realInverted = this.rest;
			real = this.restInverted;
		}
		Matrix4f delta = new Matrix4f();
		Matrix4f absolute = new Matrix4f();
		Matrix4f.mul(realInverted, real, delta);
		this.modified = (this.parent != null) ? Matrix4f.mul(this.parent.modified, delta, initModified()) : delta;
		Matrix4f.mul(real, this.modified, absolute);
		Matrix4f.invert(absolute, this.prevInverted);
		this.children.forEach(Bone::setModified);
	}

	protected Matrix4f initModified() {
		return (this.modified == null) ? (this.modified = new Matrix4f()) : this.modified;
	}

	public void applyModified() {
		AnimFrame currentFrame = this.owner.currentFrame();
		if (currentFrame != null) {
			HashMap<Integer, Matrix4f> precalcArray = (HashMap) this.animatedTransforms
					.get(currentFrame.owner.animationName);
			Matrix4f animated = (Matrix4f) precalcArray.get(Integer.valueOf(currentFrame.ID));
			Matrix4f animatedChange = new Matrix4f();
			Matrix4f.mul(animated, this.restInverted, animatedChange);
			this.modified = (this.modified == null) ? animatedChange
					: Matrix4f.mul(this.modified, animatedChange, this.modified);
		}
		this.verts.entrySet().forEach(
				entry -> ((DeformVertex) entry.getKey()).applyModified(this, ((Float) entry.getValue()).floatValue()));
		reset();
	}

	@Override
	public float setValue(float value, EnumGeomData d) {
		return value;
	}

	@Override
	public float getValue(EnumGeomData d) {
		return 0.0F;
	}
}